/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

// pipe, fork, exec the regldg
// TODO: time for a rename, going to be using for other dictionary generator
// also, eg cat /usr/share/dict/cracklib-small

#include "omf.h"


int read_fd;
bool regldg_launch(char *regex_str, size_t maxlen)
{
    int pipe_fds[2];
    pid_t pid;

    if (pipe(pipe_fds) == -1)
      {
	  perror("pipe()");
	  return false;
      }

    pid = fork();
    switch (pid)
      {
      case -1:
	  perror("fork()");
	  close(pipe_fds[0]);
	  close(pipe_fds[1]);
	  return false;
      case 0:			/* child process */
//    signal(SIGTERM, SIG_DFL);

	  debug1_printf("regldg child process: %d\n", getpid());
	  close(pipe_fds[0]);
	  //error chec dup2
	  dup2(pipe_fds[1], fileno(stdout));	/* close child's stdout and make it pipe_fds[1] */
	  //stderr to file?
	  //TODO: change this
	  //fclose(stderr);
	  char maxlen_str[10];
#if ARCH_X86
#define FMT_SIZE_T "%d"
#elif ARCH_X86_64
#define FMT_SIZE_T "%ld"
#else
#error undefined archirecture.
#endif
	  snprintf(maxlen_str, sizeof(maxlen_str), FMT_SIZE_T, maxlen);
	  char *args[10] =
	      { REGLDG_PATH, "-us", "255", "-b", "-uc", "0", "-m",
	      maxlen_str,
	      regex_str,
	      NULL
	  };
	  execv(REGLDG_PATH, args);
	  // if it got here it failed
	  perror("execv()");
	  close(pipe_fds[1]);
	  return false;
      default:			/* parent process (pipe reader) */
	  regldg_pid = pid;	// set global
	  usleep(1000);		// give the exec some time, TODO: better way of doing this?
	  close(pipe_fds[1]);
	  read_fd = pipe_fds[0];
	  return true;
      }
}


// must free return value, asymetric 
// returns NULL on error or end of permutations
uint8_t *regldg_next_permutation(size_t * out_len)
{
#define MAX_B64_STR 32768	// should be enough for now.
#define MAX_OUT_LEN 32768
    static char b64_str[MAX_B64_STR];	//hard limit for now..
    static uint8_t ret[MAX_OUT_LEN];	//may or may not be \0 terminated
    // char *b64_str = alloca(MAX_B64_STR);
    //TODO: static? does it make it faster, probably not, but don't have to worry about stack allocation problems with large sizes.
    uint8_t c;
    int i = 0;
    fd_set rfds;
    struct timeval tv;

    FD_ZERO(&rfds);
    FD_SET(read_fd, &rfds);
    tv.tv_sec = 2;
    tv.tv_usec = 0;


    while (1)
      {
	  int sr;
	  do
	    {
		sr = select(read_fd + 1, &rfds, NULL, NULL, &tv);	// if no data in 2 sec assume regldg is not ever going to print data or EOF, 
	    }
	  while (sr < 0 && errno == EINTR && usleep(50000) * 0 == 0);
	  // only want to sleep if it is looping, so shortcircuits if not, usleep expression is always true.
	  // sleep 0.05 second

	  //fprintf(stderr, "%d\n", FD_ISSET(read_fd, &rfds));
	  if (sr < 0)
	    {
		perror("select()");
		close(read_fd);
		return NULL;
	    }
	  if (sr == 0)		// no active fds, so must have been the 5 second timeout (was blocked for 5 seconds
	    {
		debug1_printf
		    ("select timed out, regldg is hanging (known regldg bug)\n");
		kill(regldg_pid, SIGTERM);
		kill(regldg_pid, SIGTERM);

		return NULL;
	    }

	  int rr = read(read_fd, &c, 1);	//will block until data available on pipe, but select already informed us thre is

	  if (rr < 0)
	    {
		// should loop for EINTR?
		//if(errno != EINTR
		perror("read()");
		close(read_fd);
		return NULL;
	    }
	  if (rr == 0)		//EOF
	    {
		close(read_fd);
		kill(regldg_pid, SIGTERM);
		kill(regldg_pid, SIGTERM);

		return NULL;

	    }
	  // else rr is > 0
	  b64_str[i] = c;
	  if (b64_str[i] == '\n')	// each base64 encoded permutation is delimited by \n
	    {
		b64_str[i] = '\0';
		break;

	    }

	  if (++i == MAX_B64_STR - 1)	// was too long
	    {
		fprintf(stderr,
			"was to long: increase MAX_B64_STR (%d).\n",
			MAX_B64_STR);
		return NULL;
	    }
      }

    debug2_printf("b64: %s\n", b64_str);	//b64 always printable

    //pbcrypto, no longer used
    //uint8_t *ar = b64_dec(b64_str, out_len, 0);

    *out_len = base64_decode(b64_str, ret, MAX_OUT_LEN);

    if (*out_len == -1)
      {
	  fprintf(stderr, "warning: base64_decode failed\n");
	  return NULL;
      }


    return ret;
}
